React パフォーマンス向上 備忘録
Reactのレンダリングの仕組み・仕様を把握しておく
hr.icon
参考記事にも書かれてるように、基本的にコンポーネントのレンダリングが起こるのは3つ
1. stateが変更された時
2. propsが変更された時
3. 親コンポーネントが再レンダリングされた時
詳細は別記事に譲るが、このレンダリングの仕組みを頭に入れながら実装をすることが、パフォーマンス改善の第一歩。
不必要なレンダリングを避けることが速度改善の第一歩
hr.icon
参考
大前提
リアルDOMを変更する量が少なかったとしても、仮装DOMをチェックする(再レンダリング)量が多くなると処理時間が長くなる。
例えば、自コンポーネントは再レンダリングする必要すらないのに、親コンポーネントが変わっただけで再レンダリングするはめになることがある。見た目は何も変わらないのに、再レンダリングするのは完全に無駄な処理。
こういう無駄を削っていくことが大事。
対策は相場:shouldComponentUpdateの仕組みを利用する。
こいつはコンポーネントが再レンダリングする前に、「レンダリングする?しない?」てのを決める関数。
boolean値を返してて、こいつがtrueならレンダリングする。falseならレンダリングしないってなる。
ただ、直接shouldComponentUpdateを利用することはない。
もっと簡易的に使えるものが用意されてる。
クラスコンポーネントだとPureComponent
関数コンポーネントだとReact.memo
こいつらを再レンダリングをうまく制限したいコンポーネントに当ててあげる。
関数コンポーネントの場合はuseCallbackやuseMemoも利用することになる。
useMemo:コンポーネント内の値に利用して、再計算を防いであげる効果がある。
関数コンポーネントの場合、レンダリングのたびに何回も計算してるので、それを防いであげる。
useCallback:propsで受け取る関数に対して、この関数でラップしておいてもらう。
React.memoの天敵は、propsで渡される関数。
こいつは親コンポーネントが再レンダリングされるたびに、インスタンスIDが変わるから、React.memoがprops値が変わったと勘違いしてしまう。
これが起きないように、親コンポーネントで子コンポーネントに与える関数に対してuseCallbackを当てておいてもらう。
この基本を頭に入れておいて、useMemoを使わない方がいい場合もある。とかは、後ででいい。
再レンダリングを防いだ方がいい場面
hr.icon
参考
プロファイラを使ってボトルネックを見つける
hr.icon
Reactは便利なデベロッパーツールを用意してくれてる。
詳しくは以下ページを参照されたし。
こいつを使って「どのコンポーネントのレンダリングが遅くなってるのか」「無駄にレンダリング回数が増えてないか」とかを調べる。
Atomicデザインでアプリを構成することで、レンダリングの制御をわかりやすくする
hr.icon
stateを持たせるコンポーネントを制御したりすることで、無駄なレンダリングが起きたり、レンダリングの連鎖がややこしいことになったりしないようにする。
参考記事
落書き
memo.icon
Reactアプリケーションは本番用ビルドを行うことで、ファイルサイズを小さくしておく
DevToolsプロファイラを使用してコンポーネントのプロファイリングをせよ
react-dom v16.5~、react-native v0.57~は開発モードにおけるプロファイリング機能を提供している。
長いデータのリスト(数100〜数1000)をレンダーする場合は「ウィンドウィング」というテクニックを使用すると良い react-window, react-virtualizedがウィンドウィング用のライブラリ
リコンシリエーション(差分検出処理)を避ける
前提
Reactの特徴は「仮想DOM」であり、常に内部にこの仮想DOMを保持してる。
この仮想DOMにより、本当のDOMノードの不要な作成・アクセスを回避する
本物DOMノードの操作はめちゃくちゃ遅い
あるコンポーネントのprops, stateが変更された場合、Reactは新しく返された要素と以前にレンダーされた要素を仮想DOM上で比較し、本物DOMノードを変更する必要があるか判断する。
Reactは差分があるところだけを基に本物DOMノードを変更する。
説明
この差分検出処理(再レンダー)は、差分だけを更新すると言っても結構時間がかかる。
この再レンダーが遅い!!と感じる場合は、再レンダーをスキップすることが可能。
shouldComponentUpdateやReact.PureComponentを利用することで、再レンダーをスキップすることができる。
https://ja.reactjs.org/static/5ee1bdf4779af06072a17b7a0654f6db/cd039/should-component-update.png
Profilerコンポーネントをを積極的に利用していく
code: sample.js
render(
<App>
<Profiler id="Navigation" onRender={callback}>
<Navigation {...props} />
</Profiler>d
<Main {...props} />
</App>
);
callbackにはonRenderコールバック関数を渡す
useRefはuseStateと同様に値を保持することができるが、値の変更をきっかけにレンダリングすることはない。
useStateの値が変わったらレンダリングが行われる。
カスタムhooksは使われるコンポーネントのことを知りません。そして、そのカスタムhooksを使うコンポーネント側もカスタムhooksの内部事情をしりません。カプセル化されている状態では関数がメモ化されているかどうかを知らないので、関数を useCallback でメモ化してあげている方が汎用的です。